//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.TridentModel
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Research.eResearch;
    using Microsoft.Research.eResearch.Common.Linq;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Collections.Specialized;

    /// <summary>
    /// The ISecurableObjectInternal representation.
    /// </summary>
    public class PermissionSettingsModel
    {
        /// <summary>
        /// The main object.
        /// </summary>
        private ISecurableObjectInternal securableObjectInternal;

        /// <summary>
        /// System policy.
        /// </summary>
        private SystemPolicy sysPolicy;

        /// <summary>
        /// Set of principals newly added.
        /// </summary>
        private Collection<SecurityPermissionModel> addedPrincipals = new Collection<SecurityPermissionModel>();

        /// <summary>
        /// Set of removed principals.
        /// </summary>
        private Collection<SecurityPermissionModel> removedPrincipals = new Collection<SecurityPermissionModel>();

        /// <summary>
        /// Set of all security principals.
        /// </summary>
        private ObservableCollection<SecurityPermissionModel> securityPrincipals = new ObservableCollection<SecurityPermissionModel>();

        /// <summary>
        /// Gets the security principals.
        /// </summary>
        /// <value>The security principals.</value>
        public ObservableCollection<SecurityPermissionModel> SecurityPrincipals
        {
            get
            {
                return this.securityPrincipals;
            }
        }

        /// <summary>
        /// Gets the securable object internal.
        /// </summary>
        /// <value>The securable object internal.</value>
        public ISecurableObjectInternal SecurableObjectInternal
        {
            get
            {
                return this.securableObjectInternal;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PermissionSettingsModel"/> class.
        /// </summary>
        /// <param name="securableObject">The securable object.</param>
        public PermissionSettingsModel(ISecurableObjectInternal securableObject, SystemPolicy sysPolicy)
        {
            this.securableObjectInternal = securableObject;
            this.sysPolicy = sysPolicy;
            this.Initialize(securableObject);
            this.SecurityPrincipals.CollectionChanged += new NotifyCollectionChangedEventHandler(this.OnSecurityPrincipalsCollectionChanged);
        }

        /// <summary>
        /// Saves the changes.
        /// </summary>
        public void SaveChanges()
        {
            if (this.securableObjectInternal != null)
            {
                bool changesExist = false;

                // Create a collection of changed security permission models, do this by querying the permission models in the current 
                // ISecurableObjectInternal and see if the processed privilege has changed for any of those models.
                Dictionary<SecurityPermissionModel, IEnumerable<PermissionModel>> changedItems = new Dictionary<SecurityPermissionModel, IEnumerable<PermissionModel>>();
                this.SecurityPrincipals.Except(this.addedPrincipals).ForEach(securityPrincipal =>
                    changedItems[securityPrincipal] = securityPrincipal.AvailablePermissions.Where(
                    availablePermission => availablePermission.IsDirty));

                // For the changed items, the privileges need to be persisted (i.e., the newly selected privileges need to be given access
                // and sibling privileges if any need to be removed access.
                foreach (SecurityPermissionModel securityPermissionModel in changedItems.Keys)
                {
                    foreach (PermissionModel permissionModel in changedItems[securityPermissionModel])
                    {
                        changesExist = true;
                        IEnumerable<PrivilegeModel> removePrivilegeModels = permissionModel.Privileges;
                        PrivilegeModel addPrivilegeModel = permissionModel.Privileges.FirstOrDefault(privilege => privilege.ProcessedPrivilege);
                        if (addPrivilegeModel != null)
                        {
                            removePrivilegeModels = removePrivilegeModels.Except(new Collection<PrivilegeModel>() { addPrivilegeModel });
                            this.securableObjectInternal.AddAccess(securityPermissionModel.Principal, addPrivilegeModel.Privilege);
                        }

                        removePrivilegeModels.ForEach(
                            removePrivilegeModel => this.securableObjectInternal.RemoveAccess(securityPermissionModel.Principal, removePrivilegeModel.Privilege));

                        permissionModel.Refresh();
                    }
                }

                // If any principal has been removed, remove the associated privileges.
                foreach (SecurityPermissionModel removedSecurityPermissionModel in this.removedPrincipals)
                {
                    foreach (PermissionModel permissionModel in removedSecurityPermissionModel.AvailablePermissions)
                    {
                        foreach (PrivilegeModel privilegeModel in permissionModel.Privileges)
                        {
                            changesExist = true;
                            this.securableObjectInternal.RemoveAccess(removedSecurityPermissionModel.Principal, privilegeModel.Privilege);
                            permissionModel.Refresh();
                        }
                    }
                }

                this.removedPrincipals.Clear();

                // For added principals, add the new processed privileges.
                foreach (SecurityPermissionModel addedSecurityPermissionModel in this.addedPrincipals)
                {
                    foreach (PermissionModel permissionModel in addedSecurityPermissionModel.AvailablePermissions)
                    {
                        foreach (PrivilegeModel privilegeModel in permissionModel.Privileges)
                        {
                            if (privilegeModel.ProcessedPrivilege)
                            {
                                changesExist = true;
                                this.securableObjectInternal.AddAccess(addedSecurityPermissionModel.Principal, privilegeModel.Privilege);
                                permissionModel.Refresh();
                            }
                        }
                    }
                }

                this.addedPrincipals.Clear();

                // If changes exist, save.
                if (changesExist)
                {
                    (this.securableObjectInternal as IObject).Save();
                    (this.securableObjectInternal as IObject).Refresh();
                }
            }
        }

        /// <summary>
        /// Refreshes the specified model to refresh.
        /// </summary>
        /// <param name="modelToRefresh">The model to refresh.</param>
        /// <returns></returns>
        public static PermissionSettingsModel Refresh(PermissionSettingsModel modelToRefresh, SystemPolicy sysPolicy)
        {
            PermissionSettingsModel refreshedModel = null;
            if (modelToRefresh != null && modelToRefresh.SecurableObjectInternal != null)
            {
                refreshedModel = new PermissionSettingsModel(modelToRefresh.SecurableObjectInternal, sysPolicy);
            }

            return refreshedModel;
        }

        /// <summary>
        /// Creates the new security principal.
        /// </summary>
        /// <param name="securityPrincipal">The security principal.</param>
        public SecurityPermissionModel CreateNewSecurityPrincipal(ISecurityPrincipal securityPrincipal)
        {
            SecurityPermissionModel securityPermissionModel = null;
            if (securityPrincipal != null)
            {
                // Check if it already exists.
                if (this.SecurityPrincipals.Any(
                    existingSecurityPrincipal => (existingSecurityPrincipal.Principal as IObject).ID.Equals((securityPrincipal as IObject).ID)))
                {
                    //TridentErrorHandler.ThrowTridentCustomException(TridentErrorConstants.ErrorNumber1300000008, "The selected principal already exists");
                }

                // Create a new security permission and assign default privilege of allow to all permissions to it.
                SecurityPermission securityPermission = new SecurityPermission();
                securityPermission.Principal = securityPrincipal;
                Collection<IPrivilege> processedPrivileges = new Collection<IPrivilege>();
                foreach (IPermission permission in this.securableObjectInternal.Permission)
                {
                    IPrivilege privilege = permission.Privileges.FirstOrDefault(item => item.DisplayName.Equals("Allow"));
                    processedPrivileges.Add(privilege);
                }

                // Add it to the current set of security principals.
                securityPermissionModel = new SecurityPermissionModel(securityPermission, processedPrivileges, this.securableObjectInternal.Permission, this.sysPolicy);
                if (securityPermissionModel.NotOwnerOrAdmin)
                {
                    securityPermissionModel.NotOwnerOrAdmin = !securityPermissionModel.Principal.Equals(this.securableObjectInternal.Owner);
                }

                this.SecurityPrincipals.Add(securityPermissionModel);
            }

            return securityPermissionModel;
        }

        /// <summary>
        /// Removes the security principal.
        /// </summary>
        /// <param name="securityPermissionModel">The security permission model.</param>
        public void RemoveSecurityPrincipal(SecurityPermissionModel securityPermissionModel)
        {
            if (securityPermissionModel != null && this.SecurityPrincipals.Contains(securityPermissionModel))
            {
                this.SecurityPrincipals.Remove(securityPermissionModel);
            }
        }

        /// <summary>
        /// Checks for changes.
        /// </summary>
        /// <returns></returns>
        public bool CheckForChanges()
        {
            bool changesExist = this.addedPrincipals.Count > 0 || this.removedPrincipals.Count > 0;
            if (!changesExist)
            {
                Dictionary<SecurityPermissionModel, IEnumerable<PermissionModel>> changedItems = new Dictionary<SecurityPermissionModel, IEnumerable<PermissionModel>>();
                this.SecurityPrincipals.ForEach(securityPrincipal =>
                    changedItems[securityPrincipal] = securityPrincipal.AvailablePermissions.Where(
                    availablePermission => availablePermission.IsDirty));

                changesExist = (changedItems.Values.Count > 0 
                                && changedItems.Values.ElementAt(0) != null
                                && changedItems.Values.ElementAt(0).Count() > 0);
            }

            return changesExist;
        }

        /// <summary>
        /// Initializes the specified securable object.
        /// </summary>
        /// <param name="securableObject">The securable object.</param>
        private void Initialize(ISecurableObjectInternal securableObject)
        {
            // Create the set of security permission models.
            if (securableObject != null)
            {
                securableObject.DACL.GetEnumerator();

                // Group all available security permissions by principals' IDs. This should give us a grouping of principals and their
                // set of privileges. From this construct a SecurityPermissionModel for each group.
                var securablesGroup = from securable in securableObject.DACL
                                      group securable by (securable.Principal as IObject).ID into securableGroup
                                      select new
                                      {
                                          Securables = securableGroup
                                      };

                securablesGroup.ForEach(securableGroup => this.SecurityPrincipals.Add(this.ModelGenerator(securableGroup.Securables, securableObject.Permission)));
                this.addedPrincipals.Clear();
                this.removedPrincipals.Clear();
            }
        }

        /// <summary>
        /// Generates the model for the ISecurableObjectInternal.
        /// </summary>
        /// <param name="securableGroup">The securable group.</param>
        /// <param name="availablePermissions">The available permissions.</param>
        /// <returns></returns>
        private SecurityPermissionModel ModelGenerator(IGrouping<Guid, SecurityPermission> securableGroup, IPermission[] availablePermissions)
        {
            SecurityPermissionModel securityPermissionModel = null;
            if (securableGroup != null && securableGroup.Count() > 0)
            {
                Collection<IPrivilege> processedPrivileges = new Collection<IPrivilege>();
                securableGroup.ForEach(securable => processedPrivileges.Add(securable.Privilege));
                securityPermissionModel = new SecurityPermissionModel(securableGroup.ElementAt(0), processedPrivileges, availablePermissions, this.sysPolicy);
                if (securityPermissionModel.NotOwnerOrAdmin)
                {
                    securityPermissionModel.NotOwnerOrAdmin = !securityPermissionModel.Principal.Equals(this.securableObjectInternal.Owner);
                }
            }

            return securityPermissionModel;
        }

        /// <summary>
        /// Called when security principals collection changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        private void OnSecurityPrincipalsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                this.addedPrincipals.Add(e.NewItems[0] as SecurityPermissionModel);
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                this.removedPrincipals.Add(e.OldItems[0] as SecurityPermissionModel);
            }
        }
    }

    /// <summary>
    /// Model for the SecurityPermission.
    /// </summary>
    public class SecurityPermissionModel : INotifyPropertyChanged
    {
        /// <summary>
        /// Indicates if owner or admin.
        /// </summary>
        private bool notOwnerOrAdmin = true;

        /// <summary>
        /// All available permissions.
        /// </summary>
        private ObservableCollection<PermissionModel> availablePermissions = new ObservableCollection<PermissionModel>();

        /// <summary>
        /// Gets or sets the display name.
        /// </summary>
        /// <value>The display name.</value>
        public string DisplayName { get; set; }

        /// <summary>
        /// Gets the available permissions.
        /// </summary>
        /// <value>The available permissions.</value>
        public ObservableCollection<PermissionModel> AvailablePermissions
        {
            get
            {
                return this.availablePermissions;
            }
        }

        /// <summary>
        /// Gets or sets the principal.
        /// </summary>
        /// <value>The principal.</value>
        public ISecurityPrincipal Principal { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether not owner or admin.
        /// </summary>
        /// <value><c>true</c> if not owner or admin; otherwise, <c>false</c>.</value>
        public bool NotOwnerOrAdmin
        {
            get
            {
                return this.notOwnerOrAdmin;
            }
            set
            {
                this.notOwnerOrAdmin = value;
                this.RaisePropertyChanged("NotOwnerOrAdmin");
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SecurityPermissionModel"/> class.
        /// </summary>
        /// <param name="securityPermission">The security permission.</param>
        /// <param name="processedPrivileges">The processed privileges.</param>
        /// <param name="availablePermissions">The available permissions.</param>
        public SecurityPermissionModel(SecurityPermission securityPermission, Collection<IPrivilege> processedPrivileges, IPermission[] availablePermissions, SystemPolicy sysPolicy)
        {
            StringBuilder dispNameBuilder = new StringBuilder();
            dispNameBuilder.Append((securityPermission.Principal as INamedObject).Name);
            if (securityPermission.Principal.Credentials.Count > 0)
            {
                dispNameBuilder.Append(" (");
                securityPermission.Principal.Credentials.ForEach(credential => dispNameBuilder.Append(credential.Credential + ", "));
                dispNameBuilder = dispNameBuilder.Remove(dispNameBuilder.Length - 2, 2);
                dispNameBuilder.Append(")");
            }

            this.DisplayName = dispNameBuilder.ToString();

            // Set the principal
            this.Principal = securityPermission.Principal;

            // Set the system policy
            if (sysPolicy != null)
            {
                this.NotOwnerOrAdmin = !(sysPolicy.Builtin.Administrators.Contains(securityPermission.Principal)
                    || sysPolicy.Builtin.Services.Contains(securityPermission.Principal));
            }

            // Maintain list of permissions.
            availablePermissions.OrderBy(permission => permission.DisplayName)
                .ForEach(permission => this.AvailablePermissions.Add(new PermissionModel(permission, processedPrivileges)));
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the property changed.
        /// </summary>
        /// <param name="name">The name.</param>
        protected void RaisePropertyChanged(string name)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }

    /// <summary>
    /// IPermission model.
    /// </summary>
    public class PermissionModel
    {
        /// <summary>
        /// Maintain the original processed privilege, so that changes made can be tracked by comparision.
        /// </summary>
        private PrivilegeModel originalProcessedPrivilege;

        /// <summary>
        /// Privilege models for the permission.
        /// </summary>
        private Collection<PrivilegeModel> privileges = new Collection<PrivilegeModel>();

        /// <summary>
        /// Gets or sets the display name.
        /// </summary>
        /// <value>The display name.</value>
        public string DisplayName { get; set; }

        /// <summary>
        /// Gets or sets the allow privilege.
        /// </summary>
        /// <value>The allow privilege.</value>
        public PrivilegeModel AllowPrivilege { get; set; }

        /// <summary>
        /// Gets or sets the deny privilege.
        /// </summary>
        /// <value>The deny privilege.</value>
        public PrivilegeModel DenyPrivilege { get; set; }

        /// <summary>
        /// Gets the privileges.
        /// </summary>
        /// <value>The privileges.</value>
        public Collection<PrivilegeModel> Privileges
        {
            get
            {
                return this.privileges;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is dirty.
        /// </summary>
        /// <value><c>true</c> if this instance is dirty; otherwise, <c>false</c>.</value>
        public bool IsDirty
        {
            get
            {
                bool isDirty = false;
                PrivilegeModel currentProcessedPrivilege = this.Privileges.FirstOrDefault(privilege => privilege.ProcessedPrivilege);
                if (!(null == this.originalProcessedPrivilege && null == currentProcessedPrivilege))
                {
                    if ((null == this.originalProcessedPrivilege && null != currentProcessedPrivilege) ||
                        (null != this.originalProcessedPrivilege && null == currentProcessedPrivilege) ||
                        !(this.originalProcessedPrivilege.Equals(currentProcessedPrivilege)))
                    {
                        isDirty = true;
                    }
                } 

                return isDirty;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PermissionModel"/> class.
        /// </summary>
        /// <param name="permission">The permission.</param>
        /// <param name="processedPrivileges">The processed privileges.</param>
        public PermissionModel(IPermission permission, IEnumerable<IPrivilege> processedPrivileges)
        {
            if (permission != null)
            {
                this.DisplayName = permission.DisplayName;
                if (permission.Privileges.Length != 2)
                {
                    throw new NotImplementedException("Privileges support is currently only for allow and deny.");
                }

                processedPrivileges = permission.Privileges.Intersect(processedPrivileges);
                IOrderedEnumerable<IPrivilege> orderedPrivileges = permission.Privileges.OrderBy(privilege => privilege.DisplayName);
                this.AllowPrivilege = new PrivilegeModel(orderedPrivileges.ElementAt(0), this, processedPrivileges.Contains(orderedPrivileges.ElementAt(0)));
                this.DenyPrivilege = new PrivilegeModel(orderedPrivileges.ElementAt(1), this, processedPrivileges.Contains(orderedPrivileges.ElementAt(1)));
                this.Privileges.Add(this.AllowPrivilege);
                this.Privileges.Add(this.DenyPrivilege);
                originalProcessedPrivilege = this.Privileges.FirstOrDefault(privilege => privilege.ProcessedPrivilege);
            }
        }

        /// <summary>
        /// Refreshes this instance.
        /// </summary>
        public void Refresh()
        {
            originalProcessedPrivilege = this.Privileges.FirstOrDefault(privilege => privilege.ProcessedPrivilege);
        }
    }

    /// <summary>
    /// The model for IPrivilege.
    /// </summary>
    public class PrivilegeModel : INotifyPropertyChanged
    {
        /// <summary>
        /// The flag indicating if processed privilege.
        /// </summary>
        private bool processedPrivilege;

        /// <summary>
        /// Gets or sets the display name.
        /// </summary>
        /// <value>The display name.</value>
        public string DisplayName { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether processed privilege.
        /// </summary>
        /// <value><c>true</c> if processed privilege; otherwise, <c>false</c>.</value>
        public bool ProcessedPrivilege
        {
            get
            {
                return this.processedPrivilege;
            }
            set
            {
                if ((bool)value)
                {
                    this.AssociatedPermissionModel.Privileges.ForEach(siblingPrivilege => siblingPrivilege.ProcessedPrivilege = false);
                }

                this.processedPrivilege = value;
                this.RaisePropertyChanged("ProcessedPrivilege");
            }
        }

        /// <summary>
        /// Gets or sets the privilege.
        /// </summary>
        /// <value>The privilege.</value>
        public IPrivilege Privilege { get; set; }

        /// <summary>
        /// Gets or sets the associated permission model.
        /// </summary>
        /// <value>The associated permission model.</value>
        public PermissionModel AssociatedPermissionModel { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="PrivilegeModel"/> class.
        /// </summary>
        /// <param name="privilege">The privilege.</param>
        /// <param name="associatedPermissionModel">The associated permission model.</param>
        /// <param name="processed">if set to <c>true</c> [processed].</param>
        public PrivilegeModel(IPrivilege privilege, PermissionModel associatedPermissionModel, bool processed)
        {
            if (privilege != null)
            {
                this.Privilege = privilege;
                this.DisplayName = privilege.DisplayName;
                this.AssociatedPermissionModel = associatedPermissionModel;
                if (processed)
                {
                    this.AssociatedPermissionModel.Privileges.ForEach(siblingPrivilege => siblingPrivilege.ProcessedPrivilege = false);
                    this.ProcessedPrivilege = processed;
                }
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        
        protected void RaisePropertyChanged(string name)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }

    /// <summary>
    /// The security principal model.
    /// </summary>
    public class SecurityPrincipalModel
    {
        /// <summary>
        /// Gets or sets the display name.
        /// </summary>
        /// <value>The display name.</value>
        public string DisplayName { get; set; }

        /// <summary>
        /// Gets or sets the type.
        /// </summary>
        /// <value>The type.</value>
        public string Type { get; set; }

        /// <summary>
        /// Gets or sets the security principal.
        /// </summary>
        /// <value>The security principal.</value>
        public ISecurityPrincipal SecurityPrincipal { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="SecurityPrincipalModel"/> class.
        /// </summary>
        /// <param name="securityPrincipal">The security principal.</param>
        public SecurityPrincipalModel(ISecurityPrincipal securityPrincipal)
        {
            if (securityPrincipal != null)
            {
                this.SecurityPrincipal = securityPrincipal;

                // Build the display name
                StringBuilder dispNameBuilder = new StringBuilder();
                dispNameBuilder.Append((securityPrincipal as INamedObject).Name);
                if (securityPrincipal.Credentials.Count > 0)
                {
                    dispNameBuilder.Append(" (");
                    securityPrincipal.Credentials.ForEach(credential => dispNameBuilder.Append(credential.Credential + ", "));
                    dispNameBuilder = dispNameBuilder.Remove(dispNameBuilder.Length - 2, 2);
                    dispNameBuilder.Append(")");
                }

                this.DisplayName = dispNameBuilder.ToString();

                // Assign the type.
                if (securityPrincipal is User)
                {
                    this.Type = "User";
                }
                else if (securityPrincipal is Group)
                {
                    this.Type = "Group";
                }
            }
        }
    }
}
